import cv2
import numpy as np
from fastapi import FastAPI, File, UploadFile, Form
from fastapi.responses import JSONResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
import uvicorn
import base64
from pydantic import BaseModel
from typing import Optional
from fastapi import Body

app = FastAPI()


# 全局配置存储
class BoxConfig(BaseModel):
    width: float = 0.33
    height_back: float = 0.24
    height_front: float = 0.08
    depth: float = 0.30


box_config = BoxConfig()

# 加载相机标定参数
with np.load('camera_params.npz') as X:
    mtx, dist = [X[i] for i in ('mtx', 'dist')]

# 定义ArUco字典和检测器
aruco_dict = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_1000)
detector = cv2.aruco.ArucoDetector(aruco_dict)


def calculate_corner_points(rvec, tvec, width, height_back, height_front, depth, aruco_offset_y):
    """
    计算料盒的八个角点在相机坐标系中的位置。

    :param rvec: ArUco 码的旋转向量
    :param tvec: ArUco 码的平移向量
    :param width: 料盒的宽度（X 轴方向）
    :param height_back: 料盒后部的高度（Z 轴方向）
    :param height_front: 料盒前部的高度（Z 轴方向）
    :param depth: 料盒的深度（Y 轴方向）
    :param aruco_offset_y: ArUco 码中心到底边的距离（Y 轴方向）
    :return: 料盒的八个角点在相机坐标系中的位置
    """
    half_width = width / 2
    half_depth = depth / 2

    # 定义料盒的八个角点（相对于 ArUco 码的位置）
    local_corners = np.array([
        # 底面
        [-half_width, -aruco_offset_y, 0],  # 左后下
        [half_width, -aruco_offset_y, 0],   # 右后下
        [half_width, height_front-aruco_offset_y, 0],   # 右前下
        [-half_width, height_front-aruco_offset_y, 0],  # 左前下
        # 顶面（后部高度）
        [-half_width, -aruco_offset_y, -depth],  # 左后上
        [half_width, -aruco_offset_y, -depth],   # 右后上
        # 顶面（前部高度）
        [half_width, height_back-aruco_offset_y, -depth],  # 右前上
        [-half_width, height_back-aruco_offset_y, -depth],  # 左前上
    ])

    # 将局部坐标转换为全局坐标（相机坐标系）
    rotation_matrix, _ = cv2.Rodrigues(rvec)
    global_corners = (rotation_matrix @ local_corners.T).T + tvec.reshape(1, 3)

    return global_corners



def process_image(image_bytes, width, height_back, height_front, depth,selected_id=None):
    print(f"[调试] 使用配置：width={width}, height_back={height_back}, height_front={height_front}, depth={depth}")
    try:
        # 转换字节为OpenCV格式
        nparr = np.frombuffer(image_bytes, np.uint8)
        frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
        original_frame = frame.copy()

        # ArUco检测
        corners, ids, _ = detector.detectMarkers(frame)
        if ids is None:
            return None, "No ArUco marker detected"

        # 处理每个检测到的标记
        results = []
        for i in range(len(ids)):
            current_id = int(ids[i][0])
            if selected_id is not None and current_id != selected_id:
                continue
            rvecs, tvecs, _ = cv2.aruco.estimatePoseSingleMarkers(corners[i], 0.043, mtx, dist)

            # 计算角点
            global_corners = calculate_corner_points(
                rvecs[0], tvecs[0],
                width=width,
                height_back=height_back,
                height_front=height_front,
                depth=depth,
                aruco_offset_y=0.035  # ArUco 码中心到底边的距离 5.8cm
            )

            # 投影到图像平面
            image_points, _ = cv2.projectPoints(
                global_corners,
                np.zeros(3),
                np.zeros(3),
                mtx,
                dist
            )
            image_points = np.int32(image_points).reshape(-1, 2)

            # 绘制图形
            cv2.aruco.drawDetectedMarkers(frame, [corners[i]], ids[i])
            cv2.drawFrameAxes(frame, mtx, dist, rvecs[0], tvecs[0], 0.05)

            # 绘制边界框
            box_edges = [
                (0, 1), (1, 2), (2, 3), (3, 0),
                (4, 5), (5, 6), (6, 7), (7, 4),
                (0, 4), (1, 5), (2, 6), (3, 7)
            ]
            for start, end in box_edges:
                cv2.line(frame, tuple(image_points[start]), tuple(image_points[end]), (0, 255, 0), 2)

            results.append({
                "id": int(ids[i][0]),
                "corners_3d": global_corners.tolist(),
                "corners_2d": image_points.tolist()
            })

        # 转换处理后的图像为base64
        if selected_id is not None:
            # Only show the selected marker
            _, img_encoded = cv2.imencode('.jpg', frame)
        else:
            # Show all markers
            _, img_encoded = cv2.imencode('.jpg', original_frame)
        img_base64 = base64.b64encode(img_encoded).decode('utf-8')

        return img_base64, results

    except Exception as e:
        return None, str(e)


# API接口定义

from pyzbar import pyzbar
from fastapi import HTTPException

@app.post("/scan_ean13")
async def scan_ean13(image: UploadFile = File(...)):
    try:
        # 读取上传的图片
        contents = await image.read()
        nparr = np.frombuffer(contents, np.uint8)
        frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)

        # 转换为灰度图像
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        # 使用pyzbar解码条形码
        barcodes = pyzbar.decode(gray)

        # 过滤出EAN-13条形码
        ean13_barcodes = [barcode for barcode in barcodes if barcode.type == 'EAN13']

        if not ean13_barcodes:
            raise HTTPException(status_code=400, detail="未检测到EAN-13条形码")

        # 提取条形码数据
        results = []
        for barcode in ean13_barcodes:
            barcode_data = barcode.data.decode("utf-8")
            results.append({
                "type": "EAN-13",
                "data": barcode_data,
                "rect": {
                    "left": barcode.rect.left,
                    "top": barcode.rect.top,
                    "width": barcode.rect.width,
                    "height": barcode.rect.height
                }
            })

        # 转换处理后的图像为base64
        _, img_encoded = cv2.imencode('.jpg', frame)
        img_base64 = base64.b64encode(img_encoded).decode('utf-8')

        return JSONResponse(content={
            "image": img_base64,
            "results": results
        })

    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/process_image")
async def process_image_api(
        image: UploadFile = File(...),
        draw_box: bool = Form(True)
):
    try:
        # 读取上传的图片
        contents = await image.read()

        # 处理图片
        img_base64, results = process_image(
            contents,
            box_config.width,
            box_config.height_back,
            box_config.height_front,
            box_config.depth
        )

        if img_base64 is None:
            return JSONResponse(
                status_code=400,
                content={"error": results}
            )

        # 返回结果
        response_data = {
            "image": img_base64,
            "results": results
        }

        return JSONResponse(content=response_data)

    except Exception as e:
        return JSONResponse(
            status_code=500,
            content={"error": str(e)}
        )


@app.post("/configure_box")
async def configure_box(config: BoxConfig = Body(...)):
    global box_config
    box_config = config
    print(f"更新配置：{config}")  # 调试输出
    return {"message": "配置更新成功"}

@app.post("/process_single_marker")
async def process_single_marker(
        image: UploadFile = File(...),
        marker_id: int = Form(...)
):
    try:
        # 读取上传的图片
        contents = await image.read()

        # 处理图片
        img_base64, results = process_image(
            contents,
            box_config.width,
            box_config.height_back,
            box_config.height_front,
            box_config.depth,
            selected_id=marker_id
        )

        if img_base64 is None:
            return JSONResponse(
                status_code=400,
                content={"error": results}
            )

        # 返回结果
        response_data = {
            "image": img_base64,
            "results": results
        }

        return JSONResponse(content=response_data)

    except Exception as e:
        return JSONResponse(
            status_code=500,
            content={"error": str(e)}
        )
# 网页测试界面
@app.get("/", response_class=HTMLResponse)
async def test_interface():
    return """
    <html>
    
        <head>
            <title>ArUco检测系统</title>
            <style>
                .container { max-width: 800px; margin: 20px auto; padding: 20px; }
                .form-section { margin-bottom: 30px; border: 1px solid #ccc; padding: 20px; border-radius: 5px; }
                .preview { margin-top: 20px; display: flex; gap: 20px; }
                img { max-width: 100%; border: 1px solid #ddd; }
                .results { background: #f5f5f5; padding: 15px; margin-top: 10px; }
            </style>
        </head>
        <body>
            <div class="container">
                <h1>ArUco盒子检测系统</h1>

              <div class="form-section">
    <h2>配置盒子尺寸（单位：米）</h2>
    <form id="configForm" onsubmit="return submitConfig(event)">
        <div>
            <label>宽度: <input type="number" step="0.01" id="width" value="0.33" required></label>
            <label>后高: <input type="number" step="0.01" id="height_back" value="0.24" required></label>
            <label>前高: <input type="number" step="0.01" id="height_front" value="0.08" required></label>
            <label>深度: <input type="number" step="0.01" id="depth" value="0.30" required></label>
        </div>
        <button type="submit">更新配置</button>
    </form>
</div>

                <div class="form-section">
    <h2>上传检测图片</h2>
    <form id="uploadForm" onsubmit="return submitImage(event)">
        <input type="file" id="imageInput" accept="image/*" required>
        <button type="submit">处理图片</button>
    </form>

    <div class="preview">

        <div>
            <h3>处理结果预览</h3>
            <img id="resultImage" style="display:none;">
            <div id="errorMessage" style="color:red;"></div>
        </div>
        <div class="results">
            <h3>检测结果</h3>
            <div>
                <label>选择ID: </label>
                <select id="idSelector" onchange="updateResult()" style="display:none;">
                    <option value="">请选择</option>
                </select>
                <button onclick="showAllMarkers()" style="display:none; margin-left: 10px;">显示全部</button>
            </div>
            <pre id="resultData"></pre>
        </div>
    </div>
</div>

<div class="form-section">
    <h2>扫描EAN-13条形码</h2>
    <form id="scanEAN13Form" onsubmit="return scanEAN13(event)">
        <input type="file" id="ean13ImageInput" accept="image/*" required>
        <button type="submit">扫描条形码</button>
    </form>

    <div class="preview">
        <div>
            <h3>处理结果预览</h3>
            <img id="ean13ResultImage" style="display:none;">
            <div id="ean13ErrorMessage" style="color:red;"></div>
        </div>
        <div class="results">
            <h3>扫描结果</h3>
            <pre id="ean13ResultData"></pre>
        </div>
    </div>
</div>


<script>
    let lastUploadedImage = null;  // 缓存所有检测结果

    // 提交图片
    async function submitImage(e) {
        e.preventDefault();
        const fileInput = document.getElementById('imageInput');
        const file = fileInput.files[0];

        if (!file) {
            showError('请选择图片文件');
            return;
        }

        const formData = new FormData();
        formData.append('image', file);
        lastUploadedImage = file;  // 保存最后上传的图片

        try {
            const response = await fetch('/process_image', {
                method: 'POST',
                body: formData
            });

            const data = await response.json();

            if (response.status !== 200) {
                showError(data.error || '处理失败');
                return;
            }

            // 显示初始的图片
            displayImage(data.image);
            // 初始化ID选择器
            initIdSelector(data.results);
            // 显示结果数据
            updateResultData(data.results);
            

            

        } catch (error) {
            showError('请求失败: ' + error.message);
        }
    }

    // 更新结果显示
    function updateResult() {
        const selector = document.getElementById('idSelector');
        const selectedId = selector.value;
        const resultData = document.getElementById('resultData');
        const fileInput = document.getElementById('imageInput');
        
        if (!fileInput.files[0]) {
        showError('请先上传图片');
        return;
    }

        if (selectedId) {
        // Show only the selected marker
        const formData = new FormData();
        formData.append('image', fileInput.files[0]);
        formData.append('marker_id', selectedId);

        fetch('/process_single_marker', {
            method: 'POST',
            body: formData
        })
        .then(response => response.json())
        .then(data => {
            const img = document.getElementById('resultImage');
            img.style.display = 'block';
            img.src = `data:image/jpeg;base64,${data.image}`;
            
            if (data.results && data.results.length > 0) {
                resultData.textContent = JSON.stringify(data.results[0], null, 2);
            } else {
                resultData.textContent = '无数据';
            }
        })
        .catch(error => {
            showError('请求失败: ' + error.message);
        });
    } else {
        // Show all markers
        resultData.textContent = '请选择一个ID';
    }
}
 function displayImage(imageData) {
        const img = document.getElementById('resultImage');
        img.style.display = 'block';
        img.src = `data:image/jpeg;base64,${imageData}`;
    }

    function initIdSelector(results) {
        const selector = document.getElementById('idSelector');
        selector.innerHTML = '<option value="">-- 请选择 --</option>';
        
        results.forEach(result => {
            const option = document.createElement('option');
            option.value = result.id;
            option.textContent = `标记 ID: ${result.id}`;
            selector.appendChild(option);
        });
        
        selector.style.display = 'inline-block';
        document.getElementById('showAllBtn').style.display = 'inline-block';
    }

function updateResult() {
        const selector = document.getElementById('idSelector');
        const selectedId = selector.value;
        
        if (!selectedId) return;
        if (!lastUploadedImage) {
            showError('请先上传图片');
            return;
        }

        const formData = new FormData();
        formData.append('image', lastUploadedImage);
        formData.append('marker_id', selectedId);

        fetch('/process_single_marker', {
            method: 'POST',
            body: formData
        })
        .then(response => response.json())
        .then(data => {
            displayImage(data.image);
            updateResultData(data.results);
        })
        .catch(error => {
            showError('标记加载失败: ' + error.message);
        });
    }
function showAllMarkers() {
        if (!lastUploadedImage) {
            showError('请先上传图片');
            return;
        }

        const formData = new FormData();
        formData.append('image', lastUploadedImage);

        fetch('/process_image', {
            method: 'POST',
            body: formData
        })
        .then(response => response.json())
        .then(data => {
            displayImage(data.image);
            document.getElementById('idSelector').value = '';
            updateResultData(data.results);
        })
        .catch(error => {
            showError('请求失败: ' + error.message);
        });
    }
 function updateResultData(results) {
        const resultData = document.getElementById('resultData');
        resultData.textContent = JSON.stringify(results, null, 2);
    }

    function showError(msg) {
        const errorElement = document.getElementById('errorMessage');
        errorElement.textContent = msg;
        setTimeout(() => errorElement.textContent = '', 5000);
    }
    // 修改配置提交函数
    async function submitConfig(e) {
    e.preventDefault();
    const formData = {
        width: parseFloat(document.getElementById('width').value),
        height_back: parseFloat(document.getElementById('height_back').value),
        height_front: parseFloat(document.getElementById('height_front').value),
        depth: parseFloat(document.getElementById('depth').value)
    };

    try {
        const response = await fetch('/configure_box', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(formData)
        });
        
        const result = await response.json();
        if (!response.ok) throw new Error(result.message || '配置失败');
        alert('配置更新成功！');
    } catch (error) {
        showError(error.message);
    }
}


async function scanEAN13(e) {
        e.preventDefault();
        const fileInput = document.getElementById('ean13ImageInput');
        const file = fileInput.files[0];

        if (!file) {
            showError('请选择图片文件');
            return;
        }

        const formData = new FormData();
        formData.append('image', file);

        try {
            const response = await fetch('/scan_ean13', {
                method: 'POST',
                body: formData
            });

            const data = await response.json();

            if (response.status !== 200) {
                showError(data.error || '扫描失败');
                return;
            }

            // 显示处理后的图片
            const img = document.getElementById('ean13ResultImage');
            img.style.display = 'block';
            img.src = `data:image/jpeg;base64,${data.image}`;

            // 显示扫描结果
            const resultData = document.getElementById('ean13ResultData');
            resultData.textContent = JSON.stringify(data.results, null, 2);

        } catch (error) {
            showError('请求失败: ' + error.message);
        }
    }

    function showError(msg) {
        document.getElementById('ean13ErrorMessage').textContent = msg;
        setTimeout(() => {
            document.getElementById('ean13ErrorMessage').textContent = '';
        }, 5000);
    }
</script>
        </body>
    </html>
    """


if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.5", port=8000)
